#ifndef _GRID_H_
#define _GRID_H_

#include <iostream>
#include <vector>
#include <hash_map>
#include <cmath>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include "polygon.h"
#include "cell.h"
#include "point.h"
#include "line.h"


#ifdef linux
namespace stdext = __gnu_cxx;
#endif

typedef std::pair<point_t, point_t> segment_t;


struct grid_t
{
   typedef std::pair<int, int> cell_idx_t;

   grid_t();
   explicit grid_t(double cell_size);
   explicit grid_t(polygon_t const & polygon, double cell_size);

   size_t size() const;
   cell_t & operator [] (size_t idx);
   cell_t const & operator [] (size_t idx) const;

   cell_t const & cell_at(point_t const & point) const;
   double cell_size() const;

   bool inside(point_t const & point) const;

   void visit(point_t const & begin, point_t const & end, boost::function<void (cell_t &, segment_t const &)> processor);
   void visit(segment_t const & segment, boost::function<void (cell_t &, segment_t const &)> processor);
   void visit(contour_t const & contour, boost::function<void (cell_t &, segment_t const &)> processor);
   void visit(polygon_t const & polygon, boost::function<void (cell_t &, segment_t const &)> processor);

   friend std::ostream & operator<< (std::ostream & out, grid_t const & grid);
   friend std::istream & operator>> (std::istream & in, grid_t & grid);

private:

   double cell_size_;
   
   cell_idx_t min_idx_;
   cell_idx_t max_idx_;
   cell_t default_cell_;

   struct cell_idx_hash
   {
       int operator() (cell_idx_t const & idx) const;
   };

   stdext::hash_map<cell_idx_t, cell_t, cell_idx_hash> data_;

   cell_idx_t cell_idx(point_t const & point) const;
   point_t cell_center(cell_idx_t const & idx) const;
   
   enum border_t
   {
       TOP_B,
       RIGHT_B,
       BOTTOM_B,
       LEFT_B
   };

   void check_grid_corners(cell_idx_t const & idx);
   segment_t get_border(border_t border_num, cell_idx_t const & idx) const;
   
   struct cell_visiter_t
   {
       cell_visiter_t(boost::function<void (cell_t &, segment_t const &)> processor, segment_t const & segment, grid_t * grid);

       void operator() (cell_idx_t const & cell);

       segment_t const & segment() const;
       line_t line() const;
       point_t const & start_point() const;
       bool move_start_point(double x);
       int step() const;

   private:
       boost::function<void (cell_t &, segment_t const &)> processor_;
       segment_t segment_;
       grid_t * grid_;
       point_t start_point_;
       int step_;
       bool segment_turned_;
   };

   cell_idx_t cell_vert_diapason(cell_idx_t const & begin, cell_idx_t const & end
                                                   , cell_visiter_t & visiter) const;
   cell_idx_t cell_hor_diapason(cell_idx_t const & begin, cell_idx_t const & end
                                                   , cell_visiter_t & visiter) const;


   bool process(cell_idx_t const & last, cell_visiter_t & visiter, cell_idx_t & next);

   void add_empty_cells();
   void set_centers_states();
   bool set_center_state(cell_t const & current, cell_t const & prev);
};

bool operator== (grid_t const & left, grid_t const & right);
bool operator!= (grid_t const & left, grid_t const & right);

#endif
